<template>
  <div class="edge-detection">
    <h1>边缘检测</h1>
    <p>这个示例展示了WebGL中的边缘检测后期处理效果。通过Sobel算子或其他卷积核来检测图像中的边缘，常用于图像处理和计算机视觉。</p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>边缘检测演示</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="demoCanvas" width="400" height="300"></canvas>
        <div class="control-panel">
          <el-form :model="edgeForm" label-width="120px">
            <el-form-item label="检测算法">
              <el-select v-model="edgeForm.algorithm" @change="updateEdgeDetection">
                <el-option label="Sobel算子" value="sobel" />
                <el-option label="Laplacian算子" value="laplacian" />
                <el-option label="Roberts算子" value="roberts" />
                <el-option label="Prewitt算子" value="prewitt" />
              </el-select>
            </el-form-item>
            <el-form-item label="边缘强度">
              <el-slider
                v-model="edgeForm.intensity"
                :min="0.1"
                :max="5.0"
                :step="0.1"
                @input="updateEdgeDetection"
              />
            </el-form-item>
            <el-form-item label="阈值">
              <el-slider
                v-model="edgeForm.threshold"
                :min="0.0"
                :max="1.0"
                :step="0.05"
                @input="updateEdgeDetection"
              />
            </el-form-item>
            <el-form-item label="反转颜色">
              <el-switch v-model="edgeForm.invert" @change="updateEdgeDetection" />
            </el-form-item>
            <el-form-item label="启用边缘检测">
              <el-switch v-model="edgeForm.enableEdge" @change="updateEdgeDetection" />
            </el-form-item>
          </el-form>
          <p>这是一个边缘检测后期处理效果，你可以：</p>
          <ul>
            <li>选择不同的边缘检测算法</li>
            <li>调整边缘检测强度</li>
            <li>设置边缘检测阈值</li>
            <li>反转输出颜色</li>
          </ul>
        </div>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>示例代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code>
// 顶点着色器
{{ VSHADER_SOURCE }}

// 片元着色器
{{ FSHADER_SOURCE }}
        </code></pre>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive, onUnmounted } from 'vue'
import { WebGLUtils } from '@/utils/webgl'

const demoCanvas = ref<HTMLCanvasElement | null>(null)
let gl: WebGLRenderingContext | null = null
let program: WebGLProgram | null = null
let webglUtils: WebGLUtils | null = null
let animationId: number | null = null
let texture: WebGLTexture | null = null

const edgeForm = reactive({
  algorithm: 'sobel',
  intensity: 1.0,
  threshold: 0.1,
  invert: false,
  enableEdge: true
})

// 顶点着色器
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  attribute vec2 a_TexCoord;
  varying vec2 v_TexCoord;
  void main() {
    gl_Position = a_Position;
    v_TexCoord = a_TexCoord;
  }
`

// 片元着色器
const FSHADER_SOURCE = `
  precision mediump float;
  uniform sampler2D u_Texture;
  uniform vec2 u_TextureSize;
  uniform int u_Algorithm; // 0: sobel, 1: laplacian, 2: roberts, 3: prewitt
  uniform float u_Intensity;
  uniform float u_Threshold;
  uniform bool u_Invert;
  uniform bool u_EnableEdge;
  varying vec2 v_TexCoord;

  void main() {
    if (!u_EnableEdge) {
      gl_FragColor = texture2D(u_Texture, v_TexCoord);
      return;
    }

    vec2 texelSize = 1.0 / u_TextureSize;
    float edge = 0.0;

    if (u_Algorithm == 0) {
      // Sobel算子
      float tl = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, -texelSize.y)).rgb);
      float tm = length(texture2D(u_Texture, v_TexCoord + vec2(0.0, -texelSize.y)).rgb);
      float tr = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, -texelSize.y)).rgb);
      float ml = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, 0.0)).rgb);
      float mr = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, 0.0)).rgb);
      float bl = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, texelSize.y)).rgb);
      float bm = length(texture2D(u_Texture, v_TexCoord + vec2(0.0, texelSize.y)).rgb);
      float br = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, texelSize.y)).rgb);

      float sobelX = (tr + 2.0 * mr + br) - (tl + 2.0 * ml + bl);
      float sobelY = (bl + 2.0 * bm + br) - (tl + 2.0 * tm + tr);
      edge = sqrt(sobelX * sobelX + sobelY * sobelY);

    } else if (u_Algorithm == 1) {
      // Laplacian算子
      float center = length(texture2D(u_Texture, v_TexCoord).rgb);
      float top = length(texture2D(u_Texture, v_TexCoord + vec2(0.0, -texelSize.y)).rgb);
      float bottom = length(texture2D(u_Texture, v_TexCoord + vec2(0.0, texelSize.y)).rgb);
      float left = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, 0.0)).rgb);
      float right = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, 0.0)).rgb);

      edge = abs(-4.0 * center + top + bottom + left + right);

    } else if (u_Algorithm == 2) {
      // Roberts算子
      float tl = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, -texelSize.y)).rgb);
      float tr = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, -texelSize.y)).rgb);
      float bl = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, texelSize.y)).rgb);
      float br = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, texelSize.y)).rgb);

      float robertsX = br - tl;
      float robertsY = bl - tr;
      edge = sqrt(robertsX * robertsX + robertsY * robertsY);

    } else if (u_Algorithm == 3) {
      // Prewitt算子
      float tl = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, -texelSize.y)).rgb);
      float tm = length(texture2D(u_Texture, v_TexCoord + vec2(0.0, -texelSize.y)).rgb);
      float tr = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, -texelSize.y)).rgb);
      float ml = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, 0.0)).rgb);
      float mr = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, 0.0)).rgb);
      float bl = length(texture2D(u_Texture, v_TexCoord + vec2(-texelSize.x, texelSize.y)).rgb);
      float bm = length(texture2D(u_Texture, v_TexCoord + vec2(0.0, texelSize.y)).rgb);
      float br = length(texture2D(u_Texture, v_TexCoord + vec2(texelSize.x, texelSize.y)).rgb);

      float prewittX = (tr + mr + br) - (tl + ml + bl);
      float prewittY = (bl + bm + br) - (tl + tm + tr);
      edge = sqrt(prewittX * prewittX + prewittY * prewittY);
    }

    edge *= u_Intensity;
    edge = edge > u_Threshold ? edge : 0.0;

    if (u_Invert) {
      edge = 1.0 - edge;
    }

    gl_FragColor = vec4(edge, edge, edge, 1.0);
  }
`

function initVertexBuffers(gl: WebGLRenderingContext): boolean {
  // 全屏四边形
  const vertices = new Float32Array([
    -1.0, -1.0, 0.0, 0.0,
     1.0, -1.0, 1.0, 0.0,
    -1.0,  1.0, 0.0, 1.0,
     1.0,  1.0, 1.0, 1.0
  ])

  const buffer = gl.createBuffer()
  if (!buffer) {
    console.error('Failed to create buffer')
    return false
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

  const a_Position = gl.getAttribLocation(program!, 'a_Position')
  const a_TexCoord = gl.getAttribLocation(program!, 'a_TexCoord')

  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 16, 0)
  gl.enableVertexAttribArray(a_Position)

  gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 16, 8)
  gl.enableVertexAttribArray(a_TexCoord)

  return true
}

function createTexture(gl: WebGLRenderingContext): WebGLTexture | null {
  const texture = gl.createTexture()
  if (!texture) return null

  // 创建一个包含几何图形的纹理用于边缘检测演示
  const canvas = document.createElement('canvas')
  canvas.width = 256
  canvas.height = 256
  const ctx = canvas.getContext('2d')!

  // 白色背景
  ctx.fillStyle = '#ffffff'
  ctx.fillRect(0, 0, 256, 256)

  // 绘制一些几何图形
  ctx.fillStyle = '#000000'

  // 矩形
  ctx.fillRect(50, 50, 80, 60)

  // 圆形
  ctx.beginPath()
  ctx.arc(180, 80, 30, 0, Math.PI * 2)
  ctx.fill()

  // 三角形
  ctx.beginPath()
  ctx.moveTo(80, 150)
  ctx.lineTo(120, 200)
  ctx.lineTo(40, 200)
  ctx.closePath()
  ctx.fill()

  // 线条
  ctx.strokeStyle = '#000000'
  ctx.lineWidth = 3
  ctx.beginPath()
  ctx.moveTo(150, 150)
  ctx.lineTo(200, 200)
  ctx.stroke()

  // 添加一些噪点
  ctx.fillStyle = '#666666'
  for (let i = 0; i < 50; i++) {
    const x = Math.random() * 256
    const y = Math.random() * 256
    ctx.fillRect(x, y, 2, 2)
  }

  gl.bindTexture(gl.TEXTURE_2D, texture)
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

  return texture
}

function updateEdgeDetection() {
  if (!gl || !program) return

  const u_Algorithm = gl.getUniformLocation(program, 'u_Algorithm')
  const u_Intensity = gl.getUniformLocation(program, 'u_Intensity')
  const u_Threshold = gl.getUniformLocation(program, 'u_Threshold')
  const u_Invert = gl.getUniformLocation(program, 'u_Invert')
  const u_EnableEdge = gl.getUniformLocation(program, 'u_EnableEdge')

  const algorithmMap: { [key: string]: number } = {
    'sobel': 0,
    'laplacian': 1,
    'roberts': 2,
    'prewitt': 3
  }

  gl.uniform1i(u_Algorithm, algorithmMap[edgeForm.algorithm])
  gl.uniform1f(u_Intensity, edgeForm.intensity)
  gl.uniform1f(u_Threshold, edgeForm.threshold)
  gl.uniform1i(u_Invert, edgeForm.invert ? 1 : 0)
  gl.uniform1i(u_EnableEdge, edgeForm.enableEdge ? 1 : 0)

  draw()
}

function draw() {
  if (!gl || !program) return

  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}

function animate() {
  draw()
  animationId = requestAnimationFrame(animate)
}

function init() {
  if (!demoCanvas.value) return

  gl = demoCanvas.value.getContext('webgl')
  if (!gl) {
    console.error('WebGL not supported')
    return
  }

  webglUtils = new WebGLUtils(gl)
  // 创建着色器
  const vertexShader = webglUtils.createShader(gl.VERTEX_SHADER, VSHADER_SOURCE)
  const fragmentShader = webglUtils.createShader(gl.FRAGMENT_SHADER, FSHADER_SOURCE)
  if (!vertexShader || !fragmentShader) return
  program = webglUtils.createProgram(vertexShader, fragmentShader)
  if (!program) {
    console.error('Failed to create program')
    return
  }

  gl.useProgram(program)

  if (!initVertexBuffers(gl)) {
    console.error('Failed to initialize vertex buffers')
    return
  }

  texture = createTexture(gl)
  if (!texture) {
    console.error('Failed to create texture')
    return
  }

  // 设置uniform变量
  const u_Texture = gl.getUniformLocation(program, 'u_Texture')
  const u_TextureSize = gl.getUniformLocation(program, 'u_TextureSize')

  gl.uniform1i(u_Texture, 0)
  gl.uniform2f(u_TextureSize, demoCanvas.value.width, demoCanvas.value.height)

  gl.activeTexture(gl.TEXTURE0)
  gl.bindTexture(gl.TEXTURE_2D, texture)

  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.viewport(0, 0, demoCanvas.value.width, demoCanvas.value.height)

  updateEdgeDetection()
  animate()
}

onMounted(() => {
  init()
})

onUnmounted(() => {
  if (animationId) {
    cancelAnimationFrame(animationId)
  }
})
</script>

<style scoped>
.edge-detection {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

h1 {
  color: #303133;
  margin-bottom: 20px;
}

p {
  line-height: 1.6;
  color: #606266;
  margin-bottom: 30px;
}

.demo-section {
  margin-bottom: 30px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.demo-content {
  display: flex;
  gap: 30px;
  align-items: flex-start;
}

canvas {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
}

.control-panel {
  flex: 1;
  min-width: 300px;
}

.control-panel p {
  margin: 20px 0 10px;
  font-weight: bold;
  color: #409EFF;
}

.control-panel ul {
  list-style-type: disc;
  padding-left: 20px;
  color: #606266;
}

.control-panel li {
  margin: 5px 0;
}

.code-section {
  margin-top: 30px;
}

.code-content {
  background-color: #f5f7fa;
  border-radius: 4px;
  overflow-x: auto;
}

.code-content pre {
  margin: 0;
  padding: 20px;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.5;
  color: #303133;
}
</style>
